using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.ObjectModel;

namespace PokerLearner.Poker
{
	public enum RoundState
	{
		SmallBlind,
		BigBlind,
		PreFlop,
		Flop,
		Turn,
		River,
		Complete
	}

	[Serializable]
	public class Round
	{
		#region Private Fields

		private int turnsLeft;

		#endregion

		#region State

		private RoundState state;
		public RoundState State
		{
			get { return state; }
		}

		#endregion

		#region Players 

		private List<Player> players;
		public List<Player> Players
		{
			get { return players; }
		}

		private int currentPlayer;
		public Player CurrentPlayer
		{
			get { return (currentPlayer >= 0 && currentPlayer < players.Count) ? players[currentPlayer] : null; }
		}

		private Player dealer;
		public Player Dealer
		{
			get { return dealer; }
		}

		private Dictionary<Player, int> playerValues;
		public Dictionary<Player, int> PlayerValues
		{
			get { return playerValues; }
		}
		
		#endregion

		#region Plays

		private List<Play> plays;
		public List<Play> Plays
		{
			get { return plays; }
		}

		public List<Play> GetAvailablePlays()
		{
			List<Play> ret = new List<Play>();
			if (State == RoundState.SmallBlind)
			{
				ret.Add(new Play(PlayType.SmallBlind, CurrentPlayer, State, SmallBlind));
			}
			else if(State == RoundState.BigBlind)
			{
				ret.Add(new Play(PlayType.BigBlind, CurrentPlayer, State, BigBlind));
			}
			else if (State == RoundState.PreFlop || State == RoundState.Flop || State == RoundState.Turn || State == RoundState.River)
			{
				int difference = splitPotValues[currentSplit] - playerValues[CurrentPlayer];

				bool alone = true;
				if (difference > 0)
					alone = false;
				else
				{
					foreach (Player player in players)
					{
						if (!player.Equals(CurrentPlayer) && player.Stack > 0)
							alone = false;
					}
				}

				if (difference == 0 || CurrentPlayer.Stack == 0)
				{
					ret.Add(new Play(PlayType.Check, CurrentPlayer, State, 0));
				}
				if (CurrentPlayer.Stack + playerValues[CurrentPlayer] >= splitPotValues[currentSplit] && difference > 0)
				{
					ret.Add(new Play(PlayType.Call, CurrentPlayer, State, difference));
				}
				if (difference == 0 && CurrentPlayer.Stack >= BigBlind && !alone)
				{
					ret.Add(new Play(PlayType.Bet, CurrentPlayer, State, BigBlind));					
				}
				if (difference > 0 && CurrentPlayer.Stack >= difference)
				{
					ret.Add(new Play(PlayType.Raise, CurrentPlayer, State, difference));
				}
				if (difference >= 0 && CurrentPlayer.Stack > 0 && !alone)
				{
					ret.Add(new Play(PlayType.AllIn, CurrentPlayer, State, CurrentPlayer.Stack));
					ret.Add(new Play(PlayType.Fold, CurrentPlayer, State, 0));
				}
			}
			return ret;
		}

		public void AddPlay(Play play)
		{
			System.Diagnostics.Debug.Assert(play.Player.Equals(CurrentPlayer));

			plays.Add(play);

			RoundState newState = state;

			// If SmallBlind, BigBlind, Call, Bet, Raise or AllIn
			if (play.Type != PlayType.Fold && play.Type != PlayType.Check)
			{
				playerValues[play.Player] += play.Value;
				splitPots[currentSplit] += play.Value;

				// This updates the stack of the player in this round.
				players[players.IndexOf(play.Player)].Stack -= play.Value;

				if (playerValues[play.Player] > splitPotValues[currentSplit])
				{
					splitPotValues[currentSplit] = playerValues[play.Player];
					turnsLeft = players.Count - 1;
				}
				else if (playerValues[play.Player] < splitPotValues[currentSplit])
				{
					int maxValue = splitPotValues[currentSplit];
					splitPotValues[currentSplit] = playerValues[play.Player];
					
					int difference = 0;
					foreach (int value in playerValues.Values)
					{
						if (value > playerValues[play.Player])
							difference += value - playerValues[play.Player];
					}
					splitPots.Add(difference);
					splitPots[currentSplit] -= difference;
					currentSplit++;

					splitPotValues.Add(maxValue);
				}
			}
			else if(play.Type == PlayType.Fold)
			{
				System.Diagnostics.Debug.WriteLine(String.Format("Player folded: {0}", play.Player));

				// Remove the player
				players.Remove(play.Player);
				
				currentPlayer--;
				
				if (players.Count == 1)
					newState = RoundState.Complete;
			}

			// If we can advance the round into the next state
			if (newState != RoundState.Complete && (play.Type == PlayType.SmallBlind || play.Type == PlayType.BigBlind || turnsLeft == 0))
				newState++;

			// Call NewPlay event handlers
			if (NewPlay != null)
				NewPlay(this, new PlayEventArgs(play));

			// Move to the next player
			currentPlayer++;
			currentPlayer = currentPlayer % players.Count;

			// If the round state changes
			if (newState != state)
			{
				System.Diagnostics.Debug.WriteLine(String.Format("Round State Changed: {0}", newState));

				turnsLeft = players.Count;
				state = newState;

				if (newState != RoundState.BigBlind && newState != RoundState.PreFlop)
					currentPlayer = 0;

				if (StateChanged != null)
					StateChanged(this, null);
			}
			
			// Call the CurrentPlayerChanged event handlers.
			if (CurrentPlayerChanged != null && State != RoundState.Complete)
				CurrentPlayerChanged(this, new PlayerEventArgs(CurrentPlayer));

			turnsLeft--;
		}

		#endregion

		#region Blinds

		private int blind;
		public int BigBlind
		{
			get { return blind; }
		}

		public int SmallBlind
		{
			get { return blind / 2; }
		}

		#endregion

		#region Common Cards

		private Card[] commonCards;
		public Card[] CommonCards
		{
			get { return commonCards; }
		}

		public void SetFlop(Card[] cards)
		{
			commonCards[0] = cards[0];
			commonCards[1] = cards[1];
			commonCards[2] = cards[2];
		}

		public void SetTurn(Card card)
		{
			commonCards[3] = card;
		}

		public void SetRiver(Card card)
		{
			commonCards[4] = card;
		}

		#endregion

		#region Pot

		public int Pot
		{
			get 
			{
				int ret = 0;
				foreach (int splitPot in splitPots)
					ret += splitPot;
				return ret; 
			}
		}

		private int currentSplit;
		private List<int> splitPotValues;
		private List<int> splitPots;

		public ReadOnlyCollection<int> SplitPots
		{
			get { return splitPots.AsReadOnly(); }
		}

		public List<Player> GetSplitPotPlayers(int index)
		{
			List<Player> ret = new List<Player>();
			foreach (Player player in players)
			{
				if (playerValues[player] >= splitPotValues[index])
					ret.Add(player);
			}
			return ret;
		}

		#endregion

		#region Constructor

		public Round(int bigBlind, List<Player> players)
		{
			dealer = players[players.Count - 1];

			currentPlayer = 0;

			currentSplit = 0;
			splitPots = new List<int>();
			splitPots.Add(0);

			splitPotValues = new List<int>();
			splitPotValues.Add(0);

			state = RoundState.SmallBlind;
			blind = bigBlind;

			commonCards = new Card[5];
			
			this.players = new List<Player>();
			this.players.AddRange(players);

			this.plays = new List<Play>();
			this.playerValues = new Dictionary<Player, int>();
			foreach (Player player in players)
			{
				playerValues.Add(player, 0);
			}

			turnsLeft = 1;
		}

		#endregion

		#region Events

		public event EventHandler StateChanged;
		public event EventHandler<PlayerEventArgs> CurrentPlayerChanged;
		public event EventHandler<PlayEventArgs> NewPlay;

		#endregion
	}
}
